home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / ViePratique / ArchiFacile / ArchiFacileSetup.exe / {app} / nw.pak / Unnamed File 000146.txt < prev    next >
Text File  |  2014-10-14  |  29KB  |  876 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. /**
  6.  * @fileoverview Touch Handler. Class that handles all touch events and
  7.  * uses them to interpret higher level gestures and behaviors. TouchEvent is a
  8.  * built in mobile safari type:
  9.  * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html.
  10.  * This class is intended to work with all webkit browsers, tested on Chrome and
  11.  * iOS.
  12.  *
  13.  * The following types of gestures are currently supported.  See the definition
  14.  * of TouchHandler.EventType for details.
  15.  *
  16.  * Single Touch:
  17.  *      This provides simple single-touch events.  Any secondary touch is
  18.  *      ignored.
  19.  *
  20.  * Drag:
  21.  *      A single touch followed by some movement. This behavior will handle all
  22.  *      of the required events and report the properties of the drag to you
  23.  *      while the touch is happening and at the end of the drag sequence. This
  24.  *      behavior will NOT perform the actual dragging (redrawing the element)
  25.  *      for you, this responsibility is left to the client code.
  26.  *
  27.  * Long press:
  28.  *     When your element is touched and held without any drag occuring, the
  29.  *     LONG_PRESS event will fire.
  30.  */
  31.  
  32. // Use an anonymous function to enable strict mode just for this file (which
  33. // will be concatenated with other files when embedded in Chrome)
  34. cr.define('cr.ui', function() {
  35.   'use strict';
  36.  
  37.   /**
  38.    * A TouchHandler attaches to an Element, listents for low-level touch (or
  39.    * mouse) events and dispatching higher-level events on the element.
  40.    * @param {!Element} element The element to listen on and fire events
  41.    * for.
  42.    * @constructor
  43.    */
  44.   function TouchHandler(element) {
  45.     /**
  46.      * @type {!Element}
  47.      * @private
  48.      */
  49.     this.element_ = element;
  50.  
  51.     /**
  52.      * The absolute sum of all touch y deltas.
  53.      * @type {number}
  54.      * @private
  55.      */
  56.     this.totalMoveY_ = 0;
  57.  
  58.     /**
  59.      * The absolute sum of all touch x deltas.
  60.      * @type {number}
  61.      * @private
  62.      */
  63.     this.totalMoveX_ = 0;
  64.  
  65.     /**
  66.      * An array of tuples where the first item is the horizontal component of a
  67.      * recent relevant touch and the second item is the touch's time stamp. Old
  68.      * touches are removed based on the max tracking time and when direction
  69.      * changes.
  70.       * @type {!Array.<number>}
  71.       * @private
  72.       */
  73.     this.recentTouchesX_ = [];
  74.  
  75.     /**
  76.      * An array of tuples where the first item is the vertical component of a
  77.      * recent relevant touch and the second item is the touch's time stamp. Old
  78.      * touches are removed based on the max tracking time and when direction
  79.      * changes.
  80.      * @type {!Array.<number>}
  81.      * @private
  82.      */
  83.     this.recentTouchesY_ = [];
  84.  
  85.     /**
  86.      * Used to keep track of all events we subscribe to so we can easily clean
  87.      * up
  88.      * @type {EventTracker}
  89.      * @private
  90.      */
  91.     this.events_ = new EventTracker();
  92.   }
  93.  
  94.  
  95.   /**
  96.    * DOM Events that may be fired by the TouchHandler at the element
  97.    */
  98.   TouchHandler.EventType = {
  99.     // Fired whenever the element is touched as the only touch to the device.
  100.     // enableDrag defaults to false, set to true to permit dragging.
  101.     TOUCH_START: 'touchHandler:touch_start',
  102.  
  103.     // Fired when an element is held for a period of time.  Prevents dragging
  104.     // from occuring (even if enableDrag was set to true).
  105.     LONG_PRESS: 'touchHandler:long_press',
  106.  
  107.     // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when
  108.     // the touch first moves sufficient distance.  enableDrag is set to true but
  109.     // can be reset to false to cancel the drag.
  110.     DRAG_START: 'touchHandler:drag_start',
  111.  
  112.     // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the
  113.     // touch is moved.
  114.     DRAG_MOVE: 'touchHandler:drag_move',
  115.  
  116.     // Fired just before TOUCH_END when a drag is released.  Correlates 1:1 with
  117.     // a DRAG_START.
  118.     DRAG_END: 'touchHandler:drag_end',
  119.  
  120.     // Fired whenever a touch that is being tracked has been released.
  121.     // Correlates 1:1 with a TOUCH_START.
  122.     TOUCH_END: 'touchHandler:touch_end',
  123.  
  124.     // Fired whenever the element is tapped in a short time and no dragging is
  125.     // detected.
  126.     TAP: 'touchHandler:tap'
  127.   };
  128.  
  129.  
  130.   /**
  131.    * The type of event sent by TouchHandler
  132.    * @constructor
  133.    * @param {string} type The type of event (one of cr.ui.Grabber.EventType).
  134.    * @param {boolean} bubbles Whether or not the event should bubble.
  135.    * @param {number} clientX The X location of the touch.
  136.    * @param {number} clientY The Y location of the touch.
  137.    * @param {!Element} touchedElement The element at the current location of the
  138.    *        touch.
  139.    */
  140.   TouchHandler.Event = function(type, bubbles, clientX, clientY,
  141.       touchedElement) {
  142.     var event = document.createEvent('Event');
  143.     event.initEvent(type, bubbles, true);
  144.     event.__proto__ = TouchHandler.Event.prototype;
  145.  
  146.     /**
  147.      * The X location of the touch affected
  148.      * @type {number}
  149.      */
  150.     event.clientX = clientX;
  151.  
  152.     /**
  153.      * The Y location of the touch affected
  154.      * @type {number}
  155.      */
  156.     event.clientY = clientY;
  157.  
  158.     /**
  159.      * The element at the current location of the touch.
  160.      * @type {!Element}
  161.      */
  162.     event.touchedElement = touchedElement;
  163.  
  164.     return event;
  165.   };
  166.  
  167.   TouchHandler.Event.prototype = {
  168.     __proto__: Event.prototype,
  169.  
  170.     /**
  171.      * For TOUCH_START and DRAG START events, set to true to enable dragging or
  172.      * false to disable dragging.
  173.      * @type {boolean|undefined}
  174.      */
  175.     enableDrag: undefined,
  176.  
  177.     /**
  178.      * For DRAG events, provides the horizontal component of the
  179.      * drag delta. Drag delta is defined as the delta of the start touch
  180.      * position and the current drag position.
  181.      * @type {number|undefined}
  182.      */
  183.     dragDeltaX: undefined,
  184.  
  185.     /**
  186.      * For DRAG events, provides the vertical component of the
  187.      * drag delta.
  188.      * @type {number|undefined}
  189.      */
  190.     dragDeltaY: undefined
  191.   };
  192.  
  193.   /**
  194.    * Maximum movement of touch required to be considered a tap.
  195.    * @type {number}
  196.    * @private
  197.    */
  198.   TouchHandler.MAX_TRACKING_FOR_TAP_ = 8;
  199.  
  200.  
  201.   /**
  202.    * The maximum number of ms to track a touch event. After an event is older
  203.    * than this value, it will be ignored in velocity calculations.
  204.    * @type {number}
  205.    * @private
  206.    */
  207.   TouchHandler.MAX_TRACKING_TIME_ = 250;
  208.  
  209.  
  210.   /**
  211.    * The maximum number of touches to track.
  212.    * @type {number}
  213.    * @private
  214.    */
  215.   TouchHandler.MAX_TRACKING_TOUCHES_ = 5;
  216.  
  217.  
  218.   /**
  219.    * The maximum velocity to return, in pixels per millisecond, that is used
  220.    * to guard against errors in calculating end velocity of a drag. This is a
  221.    * very fast drag velocity.
  222.    * @type {number}
  223.    * @private
  224.    */
  225.   TouchHandler.MAXIMUM_VELOCITY_ = 5;
  226.  
  227.  
  228.   /**
  229.    * The velocity to return, in pixel per millisecond, when the time stamps on
  230.    * the events are erroneous. The browser can return bad time stamps if the
  231.    * thread is blocked for the duration of the drag. This is a low velocity to
  232.    * prevent the content from moving quickly after a slow drag. It is less
  233.    * jarring if the content moves slowly after a fast drag.
  234.    * @type {number}
  235.    * @private
  236.    */
  237.   TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1;
  238.  
  239.   /**
  240.    * The time, in milliseconds, that a touch must be held to be considered
  241.    * 'long'.
  242.    * @type {number}
  243.    * @private
  244.    */
  245.   TouchHandler.TIME_FOR_LONG_PRESS_ = 500;
  246.  
  247.   TouchHandler.prototype = {
  248.     /**
  249.      * If defined, the identifer of the single touch that is active.  Note that
  250.      * 0 is a valid touch identifier - it should not be treated equivalently to
  251.      * undefined.
  252.      * @type {number|undefined}
  253.      * @private
  254.      */
  255.     activeTouch_: undefined,
  256.  
  257.     /**
  258.      * @type {boolean|undefined}
  259.      * @private
  260.      */
  261.     tracking_: undefined,
  262.  
  263.     /**
  264.      * @type {number|undefined}
  265.      * @private
  266.      */
  267.     startTouchX_: undefined,
  268.  
  269.     /**
  270.      * @type {number|undefined}
  271.      * @private
  272.      */
  273.     startTouchY_: undefined,
  274.  
  275.     /**
  276.      * @type {number|undefined}
  277.      * @private
  278.      */
  279.     endTouchX_: undefined,
  280.  
  281.     /**
  282.      * @type {number|undefined}
  283.      * @private
  284.      */
  285.     endTouchY_: undefined,
  286.  
  287.     /**
  288.      * Time of the touchstart event.
  289.      * @type {number|undefined}
  290.      * @private
  291.      */
  292.     startTime_: undefined,
  293.  
  294.     /**
  295.      * The time of the touchend event.
  296.      * @type {number|undefined}
  297.      * @private
  298.      */
  299.     endTime_: undefined,
  300.  
  301.     /**
  302.      * @type {number|undefined}
  303.      * @private
  304.      */
  305.     lastTouchX_: undefined,
  306.  
  307.     /**
  308.      * @type {number|undefined}
  309.      * @private
  310.      */
  311.     lastTouchY_: undefined,
  312.  
  313.     /**
  314.      * @type {number|undefined}
  315.      * @private
  316.      */
  317.     lastMoveX_: undefined,
  318.  
  319.     /**
  320.      * @type {number|undefined}
  321.      * @private
  322.      */
  323.     lastMoveY_: undefined,
  324.  
  325.     /**
  326.      * @type {number|undefined}
  327.      * @private
  328.      */
  329.     longPressTimeout_: undefined,
  330.  
  331.     /**
  332.      * If defined and true, the next click event should be swallowed
  333.      * @type {boolean|undefined}
  334.      * @private
  335.      */
  336.     swallowNextClick_: undefined,
  337.  
  338.     /**
  339.      * @type {boolean}
  340.      * @private
  341.      */
  342.     draggingEnabled_: false,
  343.  
  344.     /**
  345.      * Start listenting for events.
  346.      * @param {boolean=} opt_capture True if the TouchHandler should listen to
  347.      *      during the capture phase.
  348.      * @param {boolean=} opt_mouse True if the TouchHandler should generate
  349.      *      events for mouse input (in addition to touch input).
  350.      */
  351.     enable: function(opt_capture, opt_mouse) {
  352.       var capture = !!opt_capture;
  353.  
  354.       // Just listen to start events for now. When a touch is occuring we'll
  355.       // want to be subscribed to move and end events on the document, but we
  356.       // don't want to incur the cost of lots of no-op handlers on the document.
  357.       this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this),
  358.                        capture);
  359.       if (opt_mouse) {
  360.         this.events_.add(this.element_, 'mousedown',
  361.                          this.mouseToTouchCallback_(this.onStart_.bind(this)),
  362.                          capture);
  363.       }
  364.  
  365.       // If the element is long-pressed, we may need to swallow a click
  366.       this.events_.add(this.element_, 'click', this.onClick_.bind(this), true);
  367.     },
  368.  
  369.     /**
  370.      * Stop listening to all events.
  371.      */
  372.     disable: function() {
  373.       this.stopTouching_();
  374.       this.events_.removeAll();
  375.     },
  376.  
  377.     /**
  378.      * Wraps a callback with translations of mouse events to touch events.
  379.      * NOTE: These types really should be function(Event) but then we couldn't
  380.      * use this with bind (which operates on any type of function).  Doesn't
  381.      * JSDoc support some sort of polymorphic types?
  382.      * @param {Function} callback The event callback.
  383.      * @return {Function} The wrapping callback.
  384.      * @private
  385.      */
  386.     mouseToTouchCallback_: function(callback) {
  387.       return function(e) {
  388.         // Note that there may be synthesizes mouse events caused by touch
  389.         // events (a mouseDown after a touch-click).  We leave it up to the
  390.         // client to worry about this if it matters to them (typically a short
  391.         // mouseDown/mouseUp without a click is no big problem and it's not
  392.         // obvious how we identify such synthesized events in a general way).
  393.         var touch = {
  394.           // any fixed value will do for the identifier - there will only
  395.           // ever be a single active 'touch' when using the mouse.
  396.           identifier: 0,
  397.           clientX: e.clientX,
  398.           clientY: e.clientY,
  399.           target: e.target
  400.         };
  401.         e.touches = [];
  402.         e.targetTouches = [];
  403.         e.changedTouches = [touch];
  404.         if (e.type != 'mouseup') {
  405.           e.touches[0] = touch;
  406.           e.targetTouches[0] = touch;
  407.         }
  408.         callback(e);
  409.       };
  410.     },
  411.  
  412.     /**
  413.      * Begin tracking the touchable element, it is eligible for dragging.
  414.      * @private
  415.      */
  416.     beginTracking_: function() {
  417.       this.tracking_ = true;
  418.     },
  419.  
  420.     /**
  421.      * Stop tracking the touchable element, it is no longer dragging.
  422.      * @private
  423.      */
  424.     endTracking_: function() {
  425.       this.tracking_ = false;
  426.       this.dragging_ = false;
  427.       this.totalMoveY_ = 0;
  428.       this.totalMoveX_ = 0;
  429.     },
  430.  
  431.     /**
  432.      * Reset the touchable element as if we never saw the touchStart
  433.      * Doesn't dispatch any end events - be careful of existing listeners.
  434.      */
  435.     cancelTouch: function() {
  436.       this.stopTouching_();
  437.       this.endTracking_();
  438.       // If clients needed to be aware of this, we could fire a cancel event
  439.       // here.
  440.     },
  441.  
  442.     /**
  443.      * Record that touching has stopped
  444.      * @private
  445.      */
  446.     stopTouching_: function() {
  447.       // Mark as no longer being touched
  448.       this.activeTouch_ = undefined;
  449.  
  450.       // If we're waiting for a long press, stop
  451.       window.clearTimeout(this.longPressTimeout_);
  452.  
  453.       // Stop listening for move/end events until there's another touch.
  454.       // We don't want to leave handlers piled up on the document.
  455.       // Note that there's no harm in removing handlers that weren't added, so
  456.       // rather than track whether we're using mouse or touch we do both.
  457.       this.events_.remove(document, 'touchmove');
  458.       this.events_.remove(document, 'touchend');
  459.       this.events_.remove(document, 'touchcancel');
  460.       this.events_.remove(document, 'mousemove');
  461.       this.events_.remove(document, 'mouseup');
  462.     },
  463.  
  464.     /**
  465.      * Touch start handler.
  466.      * @param {!TouchEvent} e The touchstart event.
  467.      * @private
  468.      */
  469.     onStart_: function(e) {
  470.       // Only process single touches.  If there is already a touch happening, or
  471.       // two simultaneous touches then just ignore them.
  472.       if (e.touches.length > 1)
  473.         // Note that we could cancel an active touch here.  That would make
  474.         // simultaneous touch behave similar to near-simultaneous. However, if
  475.         // the user is dragging something, an accidental second touch could be
  476.         // quite disruptive if it cancelled their drag.  Better to just ignore
  477.         // it.
  478.         return;
  479.  
  480.       // It's still possible there could be an active "touch" if the user is
  481.       // simultaneously using a mouse and a touch input.
  482.       if (this.activeTouch_ !== undefined)
  483.         return;
  484.  
  485.       var touch = e.targetTouches[0];
  486.       this.activeTouch_ = touch.identifier;
  487.  
  488.       // We've just started touching so shouldn't swallow any upcoming click
  489.       if (this.swallowNextClick_)
  490.         this.swallowNextClick_ = false;
  491.  
  492.       this.disableTap_ = false;
  493.  
  494.       // Sign up for end/cancel notifications for this touch.
  495.       // Note that we do this on the document so that even if the user drags
  496.       // their finger off the element, we'll still know what they're doing.
  497.       if (e.type == 'mousedown') {
  498.         this.events_.add(document, 'mouseup',
  499.             this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
  500.       } else {
  501.         this.events_.add(document, 'touchend', this.onEnd_.bind(this), false);
  502.         this.events_.add(document, 'touchcancel', this.onEnd_.bind(this),
  503.             false);
  504.       }
  505.  
  506.       // This timeout is cleared on touchEnd and onDrag
  507.       // If we invoke the function then we have a real long press
  508.       window.clearTimeout(this.longPressTimeout_);
  509.       this.longPressTimeout_ = window.setTimeout(
  510.           this.onLongPress_.bind(this),
  511.           TouchHandler.TIME_FOR_LONG_PRESS_);
  512.  
  513.       // Dispatch the TOUCH_START event
  514.       this.draggingEnabled_ =
  515.           !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
  516.  
  517.       // We want dragging notifications
  518.       if (e.type == 'mousedown') {
  519.         this.events_.add(document, 'mousemove',
  520.             this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
  521.       } else {
  522.         this.events_.add(document, 'touchmove', this.onMove_.bind(this), false);
  523.       }
  524.  
  525.       this.startTouchX_ = this.lastTouchX_ = touch.clientX;
  526.       this.startTouchY_ = this.lastTouchY_ = touch.clientY;
  527.       this.startTime_ = e.timeStamp;
  528.  
  529.       this.recentTouchesX_ = [];
  530.       this.recentTouchesY_ = [];
  531.       this.recentTouchesX_.push(touch.clientX, e.timeStamp);
  532.       this.recentTouchesY_.push(touch.clientY, e.timeStamp);
  533.  
  534.       this.beginTracking_();
  535.     },
  536.  
  537.     /**
  538.      * Given a list of Touches, find the one matching our activeTouch
  539.      * identifier. Note that Chrome currently always uses 0 as the identifier.
  540.      * In that case we'll end up always choosing the first element in the list.
  541.      * @param {TouchList} touches The list of Touch objects to search.
  542.      * @return {!Touch|undefined} The touch matching our active ID if any.
  543.      * @private
  544.      */
  545.     findActiveTouch_: function(touches) {
  546.       assert(this.activeTouch_ !== undefined, 'Expecting an active touch');
  547.       // A TouchList isn't actually an array, so we shouldn't use
  548.       // Array.prototype.filter/some, etc.
  549.       for (var i = 0; i < touches.length; i++) {
  550.         if (touches[i].identifier == this.activeTouch_)
  551.           return touches[i];
  552.       }
  553.       return undefined;
  554.     },
  555.  
  556.     /**
  557.      * Touch move handler.
  558.      * @param {!TouchEvent} e The touchmove event.
  559.      * @private
  560.      */
  561.     onMove_: function(e) {
  562.       if (!this.tracking_)
  563.         return;
  564.  
  565.       // Our active touch should always be in the list of touches still active
  566.       assert(this.findActiveTouch_(e.touches), 'Missing touchEnd');
  567.  
  568.       var that = this;
  569.       var touch = this.findActiveTouch_(e.changedTouches);
  570.       if (!touch)
  571.         return;
  572.  
  573.       var clientX = touch.clientX;
  574.       var clientY = touch.clientY;
  575.  
  576.       var moveX = this.lastTouchX_ - clientX;
  577.       var moveY = this.lastTouchY_ - clientY;
  578.       this.totalMoveX_ += Math.abs(moveX);
  579.       this.totalMoveY_ += Math.abs(moveY);
  580.       this.lastTouchX_ = clientX;
  581.       this.lastTouchY_ = clientY;
  582.  
  583.       var couldBeTap =
  584.           this.totalMoveY_ <= TouchHandler.MAX_TRACKING_FOR_TAP_ ||
  585.           this.totalMoveX_ <= TouchHandler.MAX_TRACKING_FOR_TAP_;
  586.  
  587.       if (!couldBeTap)
  588.         this.disableTap_ = true;
  589.  
  590.       if (this.draggingEnabled_ && !this.dragging_ && !couldBeTap) {
  591.         // If we're waiting for a long press, stop
  592.         window.clearTimeout(this.longPressTimeout_);
  593.  
  594.         // Dispatch the DRAG_START event and record whether dragging should be
  595.         // allowed or not.  Note that this relies on the current value of
  596.         // startTouchX/Y - handlers may use the initial drag delta to determine
  597.         // if dragging should be permitted.
  598.         this.dragging_ = this.dispatchEvent_(
  599.             TouchHandler.EventType.DRAG_START, touch);
  600.  
  601.         if (this.dragging_) {
  602.           // Update the start position here so that drag deltas have better
  603.           // values but don't touch the recent positions so that velocity
  604.           // calculations can still use touchstart position in the time and
  605.           // distance delta.
  606.           this.startTouchX_ = clientX;
  607.           this.startTouchY_ = clientY;
  608.           this.startTime_ = e.timeStamp;
  609.         } else {
  610.           this.endTracking_();
  611.         }
  612.       }
  613.  
  614.       if (this.dragging_) {
  615.         this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch);
  616.  
  617.         this.removeTouchesInWrongDirection_(this.recentTouchesX_,
  618.             this.lastMoveX_, moveX);
  619.         this.removeTouchesInWrongDirection_(this.recentTouchesY_,
  620.             this.lastMoveY_, moveY);
  621.         this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  622.         this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  623.         this.recentTouchesX_.push(clientX, e.timeStamp);
  624.         this.recentTouchesY_.push(clientY, e.timeStamp);
  625.       }
  626.  
  627.       this.lastMoveX_ = moveX;
  628.       this.lastMoveY_ = moveY;
  629.     },
  630.  
  631.     /**
  632.      * Filters the provided recent touches array to remove all touches except
  633.      * the last if the move direction has changed.
  634.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  635.      *     item is the x or y component of the recent touch and the second item
  636.      *     is the touch time stamp.
  637.      * @param {number|undefined} lastMove The x or y component of the previous
  638.      *     move.
  639.      * @param {number} recentMove The x or y component of the most recent move.
  640.      * @private
  641.      */
  642.     removeTouchesInWrongDirection_: function(recentTouches, lastMove,
  643.         recentMove) {
  644.       if (lastMove && recentMove && recentTouches.length > 2 &&
  645.           (lastMove > 0 ^ recentMove > 0)) {
  646.         recentTouches.splice(0, recentTouches.length - 2);
  647.       }
  648.     },
  649.  
  650.     /**
  651.      * Filters the provided recent touches array to remove all touches older
  652.      * than the max tracking time or the 5th most recent touch.
  653.      * @param {!Array.<number>} recentTouches An array of tuples where the first
  654.      *     item is the x or y component of the recent touch and the second item
  655.      *     is the touch time stamp.
  656.      * @param {number} recentTime The time of the most recent event.
  657.      * @private
  658.      */
  659.     removeOldTouches_: function(recentTouches, recentTime) {
  660.       while (recentTouches.length && recentTime - recentTouches[1] >
  661.           TouchHandler.MAX_TRACKING_TIME_ ||
  662.           recentTouches.length >
  663.               TouchHandler.MAX_TRACKING_TOUCHES_ * 2) {
  664.         recentTouches.splice(0, 2);
  665.       }
  666.     },
  667.  
  668.     /**
  669.      * Touch end handler.
  670.      * @param {!TouchEvent} e The touchend event.
  671.      * @private
  672.      */
  673.     onEnd_: function(e) {
  674.       var that = this;
  675.       assert(this.activeTouch_ !== undefined, 'Expect to already be touching');
  676.  
  677.       // If the touch we're tracking isn't changing here, ignore this touch end.
  678.       var touch = this.findActiveTouch_(e.changedTouches);
  679.       if (!touch) {
  680.         // In most cases, our active touch will be in the 'touches' collection,
  681.         // but we can't assert that because occasionally two touchend events can
  682.         // occur at almost the same time with both having empty 'touches' lists.
  683.         // I.e., 'touches' seems like it can be a bit more up-to-date than the
  684.         // current event.
  685.         return;
  686.       }
  687.  
  688.       // This is touchEnd for the touch we're monitoring
  689.       assert(!this.findActiveTouch_(e.touches),
  690.              'Touch ended also still active');
  691.  
  692.       // Indicate that touching has finished
  693.       this.stopTouching_();
  694.  
  695.       if (this.tracking_) {
  696.         var clientX = touch.clientX;
  697.         var clientY = touch.clientY;
  698.  
  699.         if (this.dragging_) {
  700.           this.endTime_ = e.timeStamp;
  701.           this.endTouchX_ = clientX;
  702.           this.endTouchY_ = clientY;
  703.  
  704.           this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
  705.           this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
  706.  
  707.           this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch);
  708.  
  709.           // Note that in some situations we can get a click event here as well.
  710.           // For now this isn't a problem, but we may want to consider having
  711.           // some logic that hides clicks that appear to be caused by a touchEnd
  712.           // used for dragging.
  713.         }
  714.  
  715.         this.endTracking_();
  716.       }
  717.       this.draggingEnabled_ = false;
  718.  
  719.       // Note that we dispatch the touchEnd event last so that events at
  720.       // different levels of semantics nest nicely (similar to how DOM
  721.       // drag-and-drop events are nested inside of the mouse events that trigger
  722.       // them).
  723.       this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch);
  724.       if (!this.disableTap_)
  725.         this.dispatchEvent_(TouchHandler.EventType.TAP, touch);
  726.     },
  727.  
  728.     /**
  729.      * Get end velocity of the drag. This method is specific to drag behavior,
  730.      * so if touch behavior and drag behavior is split then this should go with
  731.      * drag behavior. End velocity is defined as deltaXY / deltaTime where
  732.      * deltaXY is the difference between endPosition and the oldest recent
  733.      * position, and deltaTime is the difference between endTime and the oldest
  734.      * recent time stamp.
  735.      * @return {Object} The x and y velocity.
  736.      */
  737.     getEndVelocity: function() {
  738.       // Note that we could move velocity to just be an end-event parameter.
  739.       var velocityX = this.recentTouchesX_.length ?
  740.           (this.endTouchX_ - this.recentTouchesX_[0]) /
  741.           (this.endTime_ - this.recentTouchesX_[1]) : 0;
  742.       var velocityY = this.recentTouchesY_.length ?
  743.           (this.endTouchY_ - this.recentTouchesY_[0]) /
  744.           (this.endTime_ - this.recentTouchesY_[1]) : 0;
  745.  
  746.       velocityX = this.correctVelocity_(velocityX);
  747.       velocityY = this.correctVelocity_(velocityY);
  748.  
  749.       return {
  750.         x: velocityX,
  751.         y: velocityY
  752.       };
  753.     },
  754.  
  755.     /**
  756.      * Correct erroneous velocities by capping the velocity if we think it's too
  757.      * high, or setting it to a default velocity if know that the event data is
  758.      * bad.
  759.      * @param {number} velocity The x or y velocity component.
  760.      * @return {number} The corrected velocity.
  761.      * @private
  762.      */
  763.     correctVelocity_: function(velocity) {
  764.       var absVelocity = Math.abs(velocity);
  765.  
  766.       // We add to recent touches for each touchstart and touchmove. If we have
  767.       // fewer than 3 touches (6 entries), we assume that the thread was blocked
  768.       // for the duration of the drag and we received events in quick succession
  769.       // with the wrong time stamps.
  770.       if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) {
  771.         absVelocity = this.recentTouchesY_.length < 3 ?
  772.             TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ :
  773.                 TouchHandler.MAXIMUM_VELOCITY_;
  774.       }
  775.       return absVelocity * (velocity < 0 ? -1 : 1);
  776.     },
  777.  
  778.     /**
  779.      * Handler when an element has been pressed for a long time
  780.      * @private
  781.      */
  782.     onLongPress_: function() {
  783.       // Swallow any click that occurs on this element without an intervening
  784.       // touch start event.  This simple click-busting technique should be
  785.       // sufficient here since a real click should have a touchstart first.
  786.       this.swallowNextClick_ = true;
  787.       this.disableTap_ = true;
  788.  
  789.       // Dispatch to the LONG_PRESS
  790.       this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_,
  791.           this.startTouchX_, this.startTouchY_);
  792.     },
  793.  
  794.     /**
  795.      * Click handler - used to swallow clicks after a long-press
  796.      * @param {!Event} e The click event.
  797.      * @private
  798.      */
  799.     onClick_: function(e) {
  800.       if (this.swallowNextClick_) {
  801.         e.preventDefault();
  802.         e.stopPropagation();
  803.         this.swallowNextClick_ = false;
  804.       }
  805.     },
  806.  
  807.     /**
  808.      * Dispatch a TouchHandler event to the element
  809.      * @param {string} eventType The event to dispatch.
  810.      * @param {Touch} touch The touch triggering this event.
  811.      * @return {boolean|undefined} The value of enableDrag after dispatching
  812.      *         the event.
  813.      * @private
  814.      */
  815.     dispatchEvent_: function(eventType, touch) {
  816.  
  817.       // Determine which element was touched.  For mouse events, this is always
  818.       // the event/touch target.  But for touch events, the target is always the
  819.       // target of the touchstart (and it's unlikely we can change this
  820.       // since the common implementation of touch dragging relies on it). Since
  821.       // touch is our primary scenario (which we want to emulate with mouse),
  822.       // we'll treat both cases the same and not depend on the target.
  823.       var touchedElement;
  824.       if (eventType == TouchHandler.EventType.TOUCH_START) {
  825.         touchedElement = touch.target;
  826.       } else {
  827.         touchedElement = this.element_.ownerDocument.
  828.             elementFromPoint(touch.clientX, touch.clientY);
  829.       }
  830.  
  831.       return this.dispatchEventXY_(eventType, touchedElement, touch.clientX,
  832.           touch.clientY);
  833.     },
  834.  
  835.     /**
  836.      * Dispatch a TouchHandler event to the element
  837.      * @param {string} eventType The event to dispatch.
  838.        @param {number} clientX The X location for the event.
  839.        @param {number} clientY The Y location for the event.
  840.      * @return {boolean|undefined} The value of enableDrag after dispatching
  841.      *         the event.
  842.      * @private
  843.      */
  844.     dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) {
  845.       var isDrag = (eventType == TouchHandler.EventType.DRAG_START ||
  846.           eventType == TouchHandler.EventType.DRAG_MOVE ||
  847.           eventType == TouchHandler.EventType.DRAG_END);
  848.  
  849.       // Drag events don't bubble - we're really just dragging the element,
  850.       // not affecting its parent at all.
  851.       var bubbles = !isDrag;
  852.  
  853.       var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY,
  854.           touchedElement);
  855.  
  856.       // Set enableDrag when it can be overridden
  857.       if (eventType == TouchHandler.EventType.TOUCH_START)
  858.         event.enableDrag = false;
  859.       else if (eventType == TouchHandler.EventType.DRAG_START)
  860.         event.enableDrag = true;
  861.  
  862.       if (isDrag) {
  863.         event.dragDeltaX = clientX - this.startTouchX_;
  864.         event.dragDeltaY = clientY - this.startTouchY_;
  865.       }
  866.  
  867.       this.element_.dispatchEvent(event);
  868.       return event.enableDrag;
  869.     }
  870.   };
  871.  
  872.   return {
  873.     TouchHandler: TouchHandler
  874.   };
  875. });
  876.